﻿//******************************************************************************************************
//  UnsynchronizedClientSubscription.cs - Gbtc
//
//  Copyright © 2010, Grid Protection Alliance.  All Rights Reserved.
//
//  Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
//  the NOTICE file distributed with this work for additional information regarding copyright ownership.
//  The GPA licenses this file to you under the Eclipse Public License -v 1.0 (the "License"); you may
//  not use this file except in compliance with the License. You may obtain a copy of the License at:
//
//      http://www.opensource.org/licenses/eclipse-1.0.php
//
//  Unless agreed to in writing, the subject software distributed under the License is distributed on an
//  "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
//  License for the specific language governing permissions and limitations.
//
//  Code Modification History:
//  ----------------------------------------------------------------------------------------------------
//  06/24/2011 - Ritchie
//       Generated original version of source code.
//  2/2013 - University of Illinois, Information Trust Institute, Coordinated Science Laboratory
//       Updated code to work for the SIEGate system and architecture
//
//
//******************************************************************************************************

using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using SIEGate.Core.Adapters;
using SIEGate.Core.Legacy;
using SIEGate.Core.Measurements;
using SIEGate.Core;
using SIEGate.Core.Measurements.Values;
using SIEGate.Core.Stats;

namespace GSF.TimeSeries.Transport
{
    /// <summary>
    /// Represents an unsynchronized client subscription to the <see cref="DataPublisher" />.
    /// </summary>
    public class UnsynchronizedClientSubscription : OutputAdapter, ISmartAdapter
    {
        #region [ Members ]

        // Nested Types
        public class AdapterInfo : AdapterInfoBase, ISmartAdapterInfo
        {
            private ISet<Guid> m_authorizedSignals;
 
            internal ISet<Guid> AuthorizedSignals
            {
                get
                {
                    return m_authorizedSignals;
                }
            }

            public override void ExtractConfiguration(DataSet configurationTables)
            {
                Guid subscriberID;

                base.ExtractConfiguration(configurationTables);

                // Set incoming stream IDs to the set of signals
                // authorized to the corresponding remote subscriber
                subscriberID = OutgoingStreamIDs.First();
                m_authorizedSignals = GetAuthorizedSignals(configurationTables, subscriberID);
                IncomingStreamIDs = m_authorizedSignals;
            }

            /// <summary>
            /// Gets the set of signals that the subscriber is allowed to see or subscribe to.
            /// </summary>
            /// <param name="dataSource">The configuration data set containing permissions.</param>
            /// <param name="subscriberID">The globally unique identifier for the subscriber.</param>
            /// <returns>The set of signals that the subscriber is allowed to see or subscribe to.</returns>
            public ISet<Guid> GetAuthorizedSignals(DataSet dataSource, Guid subscriberID)
            {
                string subscriberFilter = string.Format("SubscriberID = '{0}'", subscriberID);
                DataRow[] measurements = dataSource.Tables["SubscriberMeasurements"].Select(subscriberFilter);
                DataRow[] groups = dataSource.Tables["SubscriberMeasurementGroups"].Select(subscriberFilter);
                Guid signalID = Guid.Empty;

                HashSet<Guid> explicitlyAllowed = new HashSet<Guid>(
                    measurements.Where(row => row["Allowed"].ToNonNullString("0").ParseBoolean())
                                .Where(row => Guid.TryParse(row["SignalID"].ToNonNullString(), out signalID))
                                .Select(row => signalID)
                );

                HashSet<Guid> implicitlyAllowed = new HashSet<Guid>(
                    groups.Where(row => row["Allowed"].ToNonNullString("0").ParseBoolean())
                          .SelectMany(row => dataSource.Tables["MeasurementGroupMeasurements"].Select(string.Format("MeasurementGroupID = {0}", row["MeasurementGroupID"].ToNonNullString("0"))))
                          .Where(row => Guid.TryParse(row["SignalID"].ToNonNullString(), out signalID))
                          .Select(row => signalID)
                );

                HashSet<Guid> explicitlyDenied = new HashSet<Guid>(
                    measurements.Where(row => !row["Allowed"].ToNonNullString("0").ParseBoolean())
                                .Where(row => Guid.TryParse(row["SignalID"].ToNonNullString(), out signalID))
                                .Select(row => signalID)
                );

                HashSet<Guid> implicitlyDenied = new HashSet<Guid>(
                    groups.Where(row => !row["Allowed"].ToNonNullString("0").ParseBoolean())
                          .SelectMany(row => dataSource.Tables["MeasurementGroupMeasurements"].Select(string.Format("MeasurementGroupID = {0}", row["MeasurementGroupID"].ToNonNullString("0"))))
                          .Where(row => Guid.TryParse(row["SignalID"].ToNonNullString(), out signalID))
                          .Select(row => signalID)
                );

                // Explicitly allowed measurements override implicitly denied measurements
                implicitlyDenied.ExceptWith(explicitlyAllowed);

                // Combine allowed and denied measurements
                explicitlyAllowed.UnionWith(implicitlyAllowed);
                explicitlyDenied.UnionWith(implicitlyDenied);

                // All other denied measurements override allowed measurements
                explicitlyAllowed.ExceptWith(explicitlyDenied);

                return explicitlyAllowed;
            }

            /// <summary>
            /// Determines if subscriber has rights to the specified signal.
            /// </summary>
            /// <param name="dataSource"><see cref="DataSet"/> that defines the permissions.</param>
            /// <param name="subscriberID"><see cref="Guid"/> based subscriber ID.</param>
            /// <param name="signalID"><see cref="Guid"/> ID of the signal to look up.</param>
            /// <returns><c>true</c> if subscriber has rights to specified <see cref="MeasurementKey"/>; otherwise <c>false</c>.</returns>
            public bool SubscriberHasRights(DataSet dataSource, Guid subscriberID, Guid signalID)
            {
                try
                {
                    bool allowed = false;

                    // Lookup explicitly defined individual measurements
                    DataRow[] explicitMeasurement = dataSource.Tables["SubscriberMeasurements"].Select(string.Format("SubscriberID='{0}' AND SignalID='{1}'", subscriberID, signalID));

                    if (explicitMeasurement.Length > 0)
                        return explicitMeasurement.All(row => row["Allowed"].ToNonNullString("0").ParseBoolean());

                    // Lookup implicitly defined group based measurements
                    DataRow[] implicitMeasurements;

                    foreach (DataRow subscriberMeasurementGroup in dataSource.Tables["SubscriberMeasurementGroups"].Select(string.Format("SubscriberID='{0}'", subscriberID)))
                    {
                        implicitMeasurements = dataSource.Tables["MeasurementGroupMeasurements"].Select(string.Format("SignalID='{0}' AND MeasurementGroupID={1}", signalID, int.Parse(subscriberMeasurementGroup["MeasurementGroupID"].ToNonNullString("0"))));

                        if (implicitMeasurements.Length > 0)
                        {
                            // If all implicit rules allow access, we can return true;
                            // if even one rule denies access, return false
                            if (subscriberMeasurementGroup["Allowed"].ToNonNullString("0").ParseBoolean())
                                allowed = true;
                            else
                                return false;
                        }
                    }

                    return allowed;
                }
                catch (Exception ex)
                {
                    string message = string.Format("{0}{1}{2}", ex.Message, Environment.NewLine, ex.StackTrace);
                    OnLog("{0}", message);
                }

                return false;
            }

            public bool StaticInputs
            {
                get
                {
                    return false;
                }
            }
        }

        // Constants
        private const MeasurementStateFlags DataRangeMask = MeasurementStateFlags.OverRangeError | MeasurementStateFlags.UnderRangeError | MeasurementStateFlags.AlarmHigh | MeasurementStateFlags.AlarmLow | MeasurementStateFlags.WarningHigh | MeasurementStateFlags.WarningLow;
        private const MeasurementStateFlags DataQualityMask = MeasurementStateFlags.BadData | MeasurementStateFlags.SuspectData | MeasurementStateFlags.FlatlineAlarm | MeasurementStateFlags.ComparisonAlarm | MeasurementStateFlags.ROCAlarm | MeasurementStateFlags.ReceivedAsBad | MeasurementStateFlags.CalculationError | MeasurementStateFlags.CalculationWarning | MeasurementStateFlags.ReservedQualityFlag;
        private const MeasurementStateFlags TimeQualityMask = MeasurementStateFlags.BadTime | MeasurementStateFlags.SuspectTime | MeasurementStateFlags.LateTimeAlarm | MeasurementStateFlags.FutureTimeAlarm | MeasurementStateFlags.UpSampled | MeasurementStateFlags.DownSampled | MeasurementStateFlags.ReservedTimeFlag;
        private const MeasurementStateFlags SystemIssueMask = MeasurementStateFlags.SystemError | MeasurementStateFlags.SystemWarning | MeasurementStateFlags.MeasurementError;
        private const MeasurementStateFlags CalculatedValueMask = MeasurementStateFlags.CalcuatedValue;
        private const MeasurementStateFlags DiscardedValueMask = MeasurementStateFlags.DiscardedValue;

        // Events

        public event EventHandler ChangeInputs;
        #endregion

        #region [ Constructors ]

        public UnsynchronizedClientSubscription(AdapterInfo adapterInfo)
            : base(adapterInfo)
        {
            Receiver.RegisterTargetedHandler("sendThese", SendTheseHandler);
        }

        #endregion

        #region [ Properties ]

        public new AdapterInfo Info
        {
            get
            {
                return (AdapterInfo)base.Info;
            }
            set
            {
                base.Info = value;
            }
        }

        #endregion

        #region [ Methods ]

        public string GetShortStatus(int maxLength)
        {
            return string.Format("Processed measurements: {0}", m_statsCluster.GetCounterValue(StringConstants.ProcessedMeasurements)).CenterText(maxLength);
        }

        /// <summary>
        /// Handles the measurements provided to this adapter for processing.
        /// </summary>
        /// <param name="measurements">The measurements to be processed.</param>
        protected override void HandleMeasurements(IEnumerable<IMeasurement> measurements)
        {
            List<BufferBlockMeasurement> bufferBlockMeasurements = new List<BufferBlockMeasurement>();
            List<CompactMeasurement> compactMeasurements = new List<CompactMeasurement>();
            int measurementCount = 0;

            foreach (IMeasurement measurement in measurements)
            {
                if (measurement.Value is BufferBlockValue)
                {
                    bufferBlockMeasurements.Add(new BufferBlockMeasurement()
                    {
                        StreamID = measurement.StreamID,
                        Buffer = measurement.Value.GetRaw().Item1,
                        Length = measurement.Value.GetRaw().Item2
                    });
                }
                else
                {
                    compactMeasurements.Add(new CompactMeasurement()
                    {
                        SignalID = measurement.StreamID,
                        StateFlags = MapToCompactFlags(measurement.StateFlags),
                        Timestamp = measurement.Timestamp,
                        Value = Convert.ToSingle(measurement.Value.GetRaw())
                    });
                }

                measurementCount++;
            }

            m_statsCluster.AddToCounter(StringConstants.ProcessedMeasurements, measurementCount);

            OnEmit(new RawData(OutgoingStreamIDs.First(), new DataPublisherValue(compactMeasurements, bufferBlockMeasurements)));
        }

        /// <summary>
        /// Not implemented. Measurements are handled by <see cref="HandleMeasurements"/>.
        /// </summary>
        /// <param name="m">The measurement that is not handled.</param>
        protected override void HandleMeasurement(IMeasurement m)
        {
            // Necessary if this is even implemented: m_statsCluster.AddToCounter(StrStatProcessedMeasurements, 1);
            throw new NotImplementedException();
        }

        public ISet<Guid> GetInputsFor(ISet<Guid> outputs)
        {
            return IncomingStreamIDs;
        }

        private void OnChangeInputs()
        {
            if ((object)ChangeInputs != null)
                ChangeInputs(this, EventArgs.Empty);
        }

        private void SendTheseHandler(IEnumerable<object> enumerable)
        {
            IncomingStreamIDs = enumerable.Cast<HashSet<Guid>>().First();
            IncomingStreamIDs.IntersectWith(Info.AuthorizedSignals);
            OnChangeInputs();
        }

        #endregion

        #region [ Static ]

        // Static Methods

        /// <summary>
        /// Maps <see cref="MeasurementStateFlags"/> to <see cref="CompactMeasurementStateFlags"/>.
        /// </summary>
        /// <param name="stateFlags">Flags to map.</param>
        /// <returns><see cref="CompactMeasurementStateFlags"/> mapped from <see cref="MeasurementStateFlags"/>.</returns>
        private static CompactMeasurementStateFlags MapToCompactFlags(MeasurementStateFlags stateFlags)
        {
            CompactMeasurementStateFlags mappedStateFlags = CompactMeasurementStateFlags.NoFlags;

            if ((stateFlags & DataRangeMask) > 0)
                mappedStateFlags |= CompactMeasurementStateFlags.DataRange;

            if ((stateFlags & DataQualityMask) > 0)
                mappedStateFlags |= CompactMeasurementStateFlags.DataQuality;

            if ((stateFlags & TimeQualityMask) > 0)
                mappedStateFlags |= CompactMeasurementStateFlags.TimeQuality;

            if ((stateFlags & SystemIssueMask) > 0)
                mappedStateFlags |= CompactMeasurementStateFlags.SystemIssue;

            if ((stateFlags & CalculatedValueMask) > 0)
                mappedStateFlags |= CompactMeasurementStateFlags.CalculatedValue;

            if ((stateFlags & DiscardedValueMask) > 0)
                mappedStateFlags |= CompactMeasurementStateFlags.DiscardedValue;

            return mappedStateFlags;
        }

        #endregion
    }
}
